<template>
	<view class="e-calendar-month" :id="elId" ref="calendarMonthRef">
		<view class="e-calendar-month__days" >
			<view class="e-calendar-month__days__day" :style="[itemStyle]" v-for="item in dayList"
				@click="handlerClick(item)" :key="item.date.getTime()">
				<view class="e-calendar-month__days__day__item" :class="[item.markClassName]">
					<view class="e-calendar-month__days__day__item__box"
						:class="[{ 'is-active': item.isChoose },{ 'is-hide': item.otherMonth !== 'nowMonth' || item.dayHide },{'is-nowDay':item.isNowDay&&props.showNowDateDot}]">
						<text class="e-calendar-month__days__day__item__box-text">{{
						item.id }}</text>
					</view>
				</view>
			</view>
		</view>
	</view>
</template>

<script lang="ts">
	import {
		defineComponent
	} from 'vue'
	import mpMixin from "../../libs/mixin/mpMixin";
	/**
	 * Calendar 日历
	 * @description  此组件用于单个选择日期，范围选择日期等，日历被包裹在底部弹起的容器中.
	 * @tutorial https://www.uviewui.com/components/calendar.html
	 *
	 * @property {String}				mode				日期类型选择  single-选择单个日期，multiple-可以选择多个日期，range-选择日期范围 （ 默认 'single' )
	 * @property {String}				startText			mode=range时，第一个日期底部的提示文字  (默认 '开始' )
	 * @property {String}				endText				mode=range时，最后一个日期底部的提示文字 (默认 '结束' )
	 * @property {String}				color				主题色，对底部按钮和选中日期有效  (默认 ‘#3c9cff' )
	 * @property {String | Number}		minDate				最小的可选日期	 (默认 0 )
	 * @property {String | Number}		maxDate				最大可选日期  (默认 7258089600000 )
	 * @property {Array | String| Date}	defaultDate			默认选中的日期
	 * @property {String | Number}		maxCount			mode=multiple时，最多可选多少个日期  (默认 	Number.MAX_SAFE_INTEGER  )
	 * @property {String | Number}		rowHeight			日期行高 (默认 56 )
	 * @property {Boolean}				readonly	        是否为只读状态，只读状态下禁止选择日期 (默认 false )
	 * @property {String | Number}		maxRange	        日期区间最多可选天数，默认无限制，mode = range时有效
	 * @property {String}				rangePrompt	        范围选择超过最多可选天数时的提示文案，mode = range时有效
	 * @property {Boolean}				showRangePrompt	    范围选择超过最多可选天数时，是否展示提示文案，mode = range时有效 (默认 true )
	 * @property {Boolean}				allowSameDay	    是否允许日期范围的起止时间为同一天，mode = range时有效 (默认 false )
	 * @property {Boolean}				sundayStart	    	是否默认从周日开始 (默认 false )
	 * @property {String}				nowDate	    		当前日期(默认 new Date() )
	 * @property {Boolean}				showNowDateDot	    显示今天日期上的小圆点
	 * @property {Array}				markDateMore	    给指定日期添加自定义的class
	 * @property {Object}	    		customStyle			自定义样式
	 *
	 * @event {Function()} changeMonth 			切换月份时触发		返回对应月份
	 * @event {Function()} monthSelected 		选中日期时触发		返回选择日期
	 * @example <e-calendar-month  :defaultDate="defaultDateMultiple" :show="show" mode="multiple" @confirm="confirm"></e-calendar-month>
	 * */
	export default defineComponent({
		name: 'e-calendar-month',
		mixins: [mpMixin]
	})
</script>
<script lang="ts" setup>
	import {
		ref,
		watch,
		computed,
		onMounted,
		nextTick,
		toRefs,
		getCurrentInstance
	} from 'vue'
	import timeUtil, {
		type Days
	} from './util'
	import {
		sleep,
		getRect,
		getNvueRect,
		daysBetweenDates,
		empty,
		isArray,
		nanoid,
		toast
	} from '../../libs/utils'
	import {
		addUnit
	} from "../../libs/utils/_utils";
	import {
		useDefaultProps
	} from '../../libs/hooks'
	import {
		calendarMonthProps,
		calendarMonthEmits
	} from './props'
	import type {
		StyleProperties,
		calendarMonthContext
	} from "../../libs/tokens";
	import componentsProps, {
		type DefaultProps
	} from "../../libs/config/componentsProps";


	const {
		calendarMonth: calendarMonthDefaultProps
	} = toRefs(componentsProps);
	
	const ctx = getCurrentInstance()

	const _props = defineProps(calendarMonthProps)
	const props = useDefaultProps < DefaultProps['calendarMonth'] > (_props, calendarMonthDefaultProps);
	const emit = defineEmits(calendarMonthEmits)
	

	let myDate: Date = new Date()
	const nowDate = computed(() => timeUtil.dateFormat(props.value.nowDate as string))

	// 每个日期的宽度
	const width = ref(0)
	const dayList = ref < Days[] > ([])
	
	

	const itemStyle = computed(() => {
		const style: StyleProperties = {}
		// #ifdef APP-NVUE
		// 得出每个日期的宽度
		// 不进行四舍五入的形式保留2位小数
		const dayWidth = Number(
			parseFloat(`${width.value / 7}`)
			.toFixed(3)
			.slice(0, -1)
		)
		style.width = addUnit(dayWidth)
		// #endif
		style.height = addUnit(props.value.rowHeight)
		return style
	})




	const intStart = () => {
		timeUtil.sundayStart = !!props.value.sundayStart
	}
	const elId = ref(nanoid(21,true))
	const calendarMonthRef = ref()
	// 获取月份数据区域的宽度，因为nvue不支持百分比，所以无法通过css设置每个日期item的宽度
	const getWrapperWidth = () => {
		// #ifdef APP-NVUE
		getNvueRect(calendarMonthRef.value).then(size => {
			width.value = size.width!
		})
		// #endif
		// #ifndef APP-NVUE
		getRect(ctx,`#${elId.value}`).then(size => {
			width.value = size.width!
		})
		// #endif
	}

	// 选中的日期数组
	let selected: Set < number > = new Set([])

	const setSelected = (date: Date) => {
		let _value = date.getTime()
		switch (props.value.mode) {
			case 'single':
				selected.clear()
				selected.add(_value)
				getDayList()
				break
			case 'multiple':
				if (selected.has(_value)) {
					selected.delete(_value)
				} else {
					if (selected.size < (props.value.maxCount || 0)) selected.add(_value)
				}
				break
			case 'range':
				if (selected.size === 0 || selected.size >= 2) {
					// 如果原来就为0或者大于2的长度，则当前点击的日期，就是开始日期
					selected.clear()
					selected.add(_value)
				} else {
					const existsDate = new Date(Array.from(selected)[0])
					const compareStatus = timeUtil.compareDate(date, existsDate)
					if (compareStatus === 0) {
						// 如果当前选择的日期小于上一次选择的日期，则当前的日期定为开始日期
						selected.clear()
					} else {
						// 当前日期减去最大可选的日期天数，如果大于起始时间，则进行提示
						if (daysBetweenDates(date, existsDate) > (props.value.maxRange || 0) && props.value
							.showRangePrompt) {
							if (props.value.rangePrompt) {
								toast(props.value.rangePrompt)
							} else {
								toast(`选择天数不能超过 ${props.value.maxRange} 天`)
							}
							return
						}
					}
					selected.add(_value)
				}
				break
		}
		let value = Array.from(selected)
		emit('monthSelected', value)
		emit('confirm', value)

	}

	const handlerClick = (item: Days) => {
		if (props.value.readonly) return
		!item.noClick && setSelected(item.date)
		if (item.otherMonth === 'nowMonth') {
			!item.dayHide && getDayList()
		} else {
			item.otherMonth === 'preMonth' ? preMonth() : nextMonth()
		}
	}


	const preMonth: calendarMonthContext['toPreMonth'] = () => {
		if (props.value.readonly) return
		myDate = timeUtil.getOtherMonth(myDate, 'preMonth')
		emit('changeMonth', myDate)
		getDayList()
	}
	const nextMonth: calendarMonthContext['toNextMonth'] = () => {
		if (props.value.readonly) return
		myDate = timeUtil.getOtherMonth(myDate, 'nextMonth')
		emit('changeMonth', myDate)
		getDayList()
	}
	const setMonth: calendarMonthContext['setMonth'] = (newDate) => {
		if (props.value.readonly) return
		myDate = new Date(newDate)
		emit('changeMonth', myDate)
		getDayList()
	}




	const markDateMore = computed(() => {
		if (props.value.markDateMore && props.value.markDateMore.length) {
			return props.value.markDateMore.map((k) => {
				return {
					date: timeUtil.dateFormat(k.date),
					className: k.className
				}
			})
		}
		return []
	})

	const getDayStatus = (date: Date) => {
		let status = false
		let _date = date.getTime()
		switch (props.value.mode) {
			case 'single':
			case 'multiple':
				status = selected.has(_date)
				break
			case 'range':
				let arr = Array.from(selected)
				if (arr[0]) {
					if (arr[1]) {
						// 判断当前日期是否在开始日期与结束日期之间
						status = timeUtil.compareDate(date, new Date(arr[0])) > 0 && timeUtil.compareDate(date,
							new Date(arr[1])) !== 1
					} else {
						status = timeUtil.compareDate(date, new Date(arr[0])) === 2
					}

				}
				break
		}
		return status
	}
	const getDayList = () => {
		let arr = timeUtil.getMonthList(myDate)

		for (let i = 0; i < arr.length; i++) {
			let k = arr[i]
			let kDate = timeUtil.dateFormat(k.date.getTime())
			let markClassName = markDateMore.value.find(item => item.date === kDate)?.className || ''
			const t = k.date.getTime()
			//标记选中某些天 设置class
			k.markClassName = markClassName

			k.isChoose = getDayStatus(k.date)
			
			//无法选中某天
			k.noClick = t < Number(props.value.minDate)
			//非本月或无法选中
			k.dayHide = k.noClick || t > Number(props.value.maxDate)
			// 判断是否为当天日期
			if (kDate === nowDate.value) {
				k.isNowDay = true
			}
		}
		dayList.value = arr
	}

	
	



	const init = () => {
		// 初始化默认选中
		if (!empty(props.value.defaultDate)) {
			if (isArray(props.value.defaultDate)) {
				props.value.defaultDate.forEach(item => {
					selected.add(new Date(item).getTime())
				})
			} else {
				selected.add(new Date(props.value.defaultDate!).getTime())
				
			}
		}
		emit('monthSelected', Array.from(selected))
		intStart()
		nextTick(() => {
			// 这里需要另一个延时，因为获取宽度后，会进行月份数据渲染，只有渲染完成之后，才有真正的高度
			// 因为nvue下，$nextTick并不是100%可靠的
			sleep(10).then(() => {
				getWrapperWidth()
				getDayList()
			})
		})
	}

	const watchProp = computed(() => [ props.value.markDateMore, props.value.minDate, props.value
		.maxDate
	])

	watch(() => watchProp.value, () => {
		getDayList()
	}, {
		deep: true
	})
	watch(() => props.value.sundayStart, () => {
		intStart()
		getDayList()
	})

	onMounted(() => {
		init()
	})


	defineExpose({
		/** @description switch to the previous month */
		toPreMonth: preMonth,
		/** @description switch to the next month */
		toNextMonth: nextMonth,
		/** @description set now month */
		setMonth

	})
</script>


<style lang="scss" scoped>
	@import "../../libs/theme/calendar-month.scss";
</style>
